#include <elasticlient/client.h>
#include <cpr/cpr.h>
#include <json/json.h>
#include <iostream>
#include <memory>
#include "logger.hpp"

//ES的二次封装, 原因: 为了简化ES的使用操作, 我们可以看到, 请求的时候, 正文很长, 我们希望只设置我们关心的参数即可, 而且能自动的构造完成
//封装四个操作: 索引创建, 数据新增, 数据查询, 数据删除

namespace wufan_im{
bool UnSerialize(const std::string& src, Json::Value& val)
{
    // 同样的Read类, 需要先构造出工厂类
    Json::CharReaderBuilder crb;
    std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
    std::string err;
    bool ret = cr->parse(src.c_str(), src.c_str() + src.size(), &val, &err);
    if (ret == false) {
        std::cout << "json反序列化失败: " << err << std::endl;
        return false;
    }
    return true;
}

bool Serialize(const Json::Value& val, std::string& dst)
{
    // Writer(StreamWriter)类, 这个类就是用来序列化的, 但是这个类不能直接构造, 因为使用了工厂模式
    // 先定义Json::SreamWriter 工厂类 Json::StreamWriterBuilder
    Json::StreamWriterBuilder swb;  //构造出工厂类
    std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
    // 通过Json::StreamWriter中的write接口进行序列化
    std::stringstream ss;
    int ret = sw->write(val, &ss);  //将其序列化到字符流里面
    if (ret != 0) {
        std::cout << "Json反序列化失败!\n";
        return false;
    }
    dst = ss.str();
    return true;
}

// 索引创建:
// 传两个参数, 索引名称 和 索引类型 就可以创建出索引
// 能够添加字段, 并设置字段类型, 设置分词器类型, 是否构造索引
class ESIndex{
    public:
        ESIndex(std::shared_ptr<elasticlient::Client>& client, const std::string& name, const std::string& type = "_doc")
        :_name(name), _type(type), _client(client)
        {
            Json::Value analysis;   //可以把Value当做Json里的{ }
            Json::Value analyzer;
            Json::Value ik;
            Json::Value tokenizer;
            tokenizer["tokenizer"] = "ik_max_word";
            ik["ik"] = tokenizer;
            analyzer["analyzer"] = ik;
            analysis["analysis"] = analyzer;
            _index["settings"] = analysis;
        }
        // 创建索引, 就相当于在设置表结构 - ai说的
        // 添加字段, 就相当于设置表的字段属性
        ESIndex& append(const std::string& key, const std::string& type = "text", 
        const std::string& analyzer = "ik_max_word", bool enabled = true) {
            Json::Value fields;
            fields["type"] = type;
            fields["analyzer"] = analyzer;
            if (enabled == false) fields["enabled"] = enabled;
            _properties[key] = fields;
            return *this;
        }
        bool create(const std::string& index_id = "default_index_id") {
            Json::Value mappings;
            mappings["dynamic"] = true;
            mappings["properties"] = _properties;
            _index["mappings"] = mappings;

            std::string body;
            bool ret = Serialize(_index, body);
            if (ret == false) {
                LOG_ERROR("索引序列化失败! ");
                return false;
            }
            
            LOG_DEBUG("{}", body);
            // 2. 发起搜索请求
            try{    //因为请求失败就可能会抛异常, 异常你不接住, 程序就会崩溃
                auto rsp = _client->index(_name, _type, index_id, body);
                if (rsp.status_code < 200 || rsp.status_code >= 300) {
                    LOG_ERROR("创建ES索引 {} 失败, 响应状态码异常: {}", _name, rsp.status_code);
                    return false;
                }
            } catch(std::exception& e) {
                LOG_ERROR("创建ES索引 {} 失败: {}", _name, e.what());
                return false;
            }
            return true;
        }
    private:
        std::string _name;
        std::string _type;
        Json::Value _properties;
        Json::Value _index;
        std::shared_ptr<elasticlient::Client> _client;
};

// 数据新增
class ESInsert{
    public:
        ESInsert(std::shared_ptr<elasticlient::Client>& client, const std::string& name,
                 const std::string& type = "_doc")
        :_name(name), _type(type), _client(client)
        {}
        ESInsert& append(const std::string& key, const std::string& val){
            _item[key] = val;
            return *this;
        }
        // 插入到哪个id里面 -  这个ID就相当于是每一次插入时数据的唯一标识
        bool insert(const std::string id = ""){
            std::string body;
            bool ret = Serialize(_item, body);
            if (ret == false) {
                LOG_ERROR("索引序列化失败! ");
                return false;
            }
            
            LOG_DEBUG("{}", body);
            // 2. 发起搜索请求
            try{    //因为请求失败就可能会抛异常, 异常你不接住, 程序就会崩溃
                auto rsp = _client->index(_name, _type, id, body);
                if (rsp.status_code < 200 || rsp.status_code >= 300) {
                    LOG_ERROR("新增数据 {} 失败, 响应状态码异常: {}", body, rsp.status_code);
                    return false;
                }
            } catch(std::exception& e) {
                LOG_ERROR("新增数据 {} 失败: {}", body, e.what());
                return false;
            }
            return true;
        }
    private:
        std::string _name;
        std::string _type;
        Json::Value _item;
        std::shared_ptr<elasticlient::Client> _client;
};


// 数据删除
class ESRemove{
    public:
        ESRemove(std::shared_ptr<elasticlient::Client>& client, const std::string& name, const std::string& type)
        :_name(name), _type(type), _client(client)
        {}
        bool remove(const std::string& id) {
            try{    //因为请求失败就可能会抛异常, 异常你不接住, 程序就会崩溃
                auto rsp = _client->remove(_name, _type, id);
                if (rsp.status_code < 200 || rsp.status_code >= 300) {
                    LOG_ERROR("删除数据 {} 失败, 响应状态码异常: {}", id, rsp.status_code);
                    return false;
                }
            } catch(std::exception& e) {
                LOG_ERROR("删除数据 {} 失败: {}", id, e.what());
                return false;
            }
            return true;
        }
    private:
        std::string _name;
        std::string _type;
        std::shared_ptr<elasticlient::Client> _client;
};


//数据查询
class ESSearch{
    public: //用户还会设置过滤条件，以及应该包含的字段
        ESSearch(std::shared_ptr<elasticlient::Client>& client, const std::string& name, const std::string& type = "_doc")
        :_name(name), _type(type), _client(client)
        {}
        ESSearch& append_must_not_terms(const std::string& key, const std::vector<std::string>& vals){
            Json::Value fields;
            for (const auto& val : vals) {
                fields[key].append(val);
            }
            Json::Value terms;
            terms["terms"] = fields;
            _must_not.append(terms);
            return *this;
        }
        ESSearch& append_should_match(const std::string& key, const std::string& val) {
            Json::Value field;
            field[key] = val;
            Json::Value match;
            match["match"] = field;
            _should.append(match);
            return *this;
        }
        Json::Value search() {
            Json::Value cond;
            if (_must_not.empty() == false) cond["must_not"] = _must_not;
            if (_should.empty() == false) cond["should"] = _should;
            Json::Value query;
            query["bool"] = cond;
            Json::Value root;
            root["query"] = query;
            std::string body;
            bool ret = Serialize(root, body);
            if (ret == false) {
                LOG_ERROR("索引序列化失败! ");
                return Json::Value();
            }
            LOG_DEBUG("{}", body);
            // 2. 发起搜索请求
            cpr::Response rsp;
            try{    //因为请求失败就可能会抛异常, 异常你不接住, 程序就会崩溃
                rsp = _client->search(_name, _type, body);
                if (rsp.status_code < 200 || rsp.status_code >= 300) {
                    LOG_ERROR("检索数据 {} 失败, 响应状态码异常: {}", body, rsp.status_code);
                    return Json::Value();
                }
            } catch(std::exception& e) {
                LOG_ERROR("检索数据 {} 失败: {}", body, e.what());
                return Json::Value();
            }
            //3. 需要对响应正文进行反序列化
            LOG_DEBUG("检索响应正文: [{}]", rsp.text);
            Json::Value json_res;
            ret = UnSerialize(rsp.text, json_res);
            if (ret == false) {
                LOG_ERROR("检索数据 {} 结果反序列化失败", rsp.text);
                return Json::Value();
            }
            return json_res["hits"]["hits"];
        }
    private:
        std::string _name;
        std::string _type;
        //用户还会设置过滤条件，以及应该包含的字段
        Json::Value _must_not;  //必须不包含的
        Json::Value _should;    //必须包含的, 多选一即可
        std::shared_ptr<elasticlient::Client> _client;
};
}